﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Core;
using System.Xml.Serialization;
using Extensil.Common.CategoryHandler;
using System.Reflection;
using Extensil.Common;
using System.Windows.Forms;
using System.ComponentModel;
using Extensil.Common.Progress;
using Extensil.Processor.Extensibility;
using Extensil.Common.CategoryHandler.Discovery;
namespace Extensil.Processor
{
    public class PluginManager
    {
        public delegate void ReportStatus(int progress, string message);
        const string _pluginFolder = "_tempPlugin";
        string _pluginPath = String.Empty;
        ReportStatus _rs;
        public PluginManager(ReportStatus rs)
        {
            _rs = rs;
        }
        public PluginManager(string pluginPath, ReportStatus rs)
        {
            _pluginPath = pluginPath;
            _rs = rs;
        }
        public static void Rename(string oldCategoryName, string newCategoryName)
        {
            PluginHandler catOld = CategorySection.GetConfig().CategoryHandlers[oldCategoryName];
            if(catOld == null)
                throw new Exception("Could not find a category with name '"+oldCategoryName+"'.");

            if(CategorySection.GetConfig().CategoryHandlers[newCategoryName] != null)
                throw new Exception("Another category with same name already exists.");
            using(ConfigModifier cm = new ConfigModifier())
            {
                catOld.Name = newCategoryName;
                cm.EditCategoryHandler(oldCategoryName, catOld);
            }
        }
        public bool Uninstall(string categoryName)
        {
            try
            {
                PluginHandler cat = CategorySection.GetConfig().CategoryHandlers[categoryName];
                _rs(10, "Removing configuration of category " + cat.Name);

                using(ConfigModifier cm = new ConfigModifier())
                {
                    cm.RemoveCategoryHandler(cat);
                }
                _rs(10, "Configuration for category '" + cat.Name + "' is removed.");

                if(cat.HasLinkedHandler)
                    _rs(50, "Category '" + cat.Name + "' has linked handler with another category. Hence will not be deleted.");
                else
                {
                    string[] catClass = cat.Path.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    string hpath = PluginAssembly.GetPluginHandler(catClass[1].Trim());
                    File.Delete(hpath);
                    _rs(100, "Handler for category '" + cat.Name + "' has been deleted.");
                }
                return true;
            }
            catch(Exception exc)
            {
                _rs(100,"Error: " + exc.Message);
                return false;
            }
        }
        public InstallResult Install()
        {
            try
            {
                if(String.IsNullOrEmpty(_pluginPath) || !File.Exists(_pluginPath))
                    throw new Exception("Plugin path must be a valid Zipped folder.");

                if(Directory.Exists(_pluginFolder))
                    Directory.Delete(_pluginFolder, true);
                Directory.CreateDirectory(_pluginFolder);

                ExtractZipFile();

                if(!File.Exists(_pluginFolder + "/Plugin.xml"))
                    throw new Exception("Invalid Plugin Content.");

                InstallResult result = ConfigurePlugin(_pluginFolder + "/Plugin.xml");
                if(result.IsSuccessFull)
                    ConfigSystem.Install();
                return result;
            }
            catch(Exception exc)
            {
                _rs(100, "Error: " + LinkedException(exc));
                return InstallResult.Create(new Exception("Error occured while installing." + LinkedException(exc)));
            }
            finally
            {
                if(!Directory.Exists(_pluginFolder))
                    Directory.Delete(_pluginFolder, true);
            }
        }
        public static void MoveCategoryUp(string catName)
        {
            using(ConfigModifier cm = new ConfigModifier())
            {
                cm.MoveCategoryHandler(catName, MoveDirection.Up);
            }
        }
        public static void MoveCategoryDown(string catName)
        {
            using(ConfigModifier cm = new ConfigModifier())
            {
                cm.MoveCategoryHandler(catName, MoveDirection.Down);
            }
        }
        InstallResult ConfigurePlugin(string plugin)
        {
            InstallResult result = new InstallResult();
            ExtensilPlugin Extensil = new ExtensilPlugin();
            using(StreamReader objStrRdr = new StreamReader(plugin))
            {
                XmlSerializer objXmlSer = new XmlSerializer(typeof(ExtensilPlugin));
                Extensil = (ExtensilPlugin)objXmlSer.Deserialize(objStrRdr);
            }
            int i = 0;
            foreach(Plugin cPlug in Extensil.CategoryHandlers)
            {
                i++;
                try
                {
                    if(ConfigureCategoryHandler(cPlug))
                        _rs(((i * 100) / Extensil.CategoryHandlers.Count), "Successfully installed category plugin '" + cPlug.Name + "'.");
                    else
                        _rs(((i * 100) / Extensil.CategoryHandlers.Count), "Installation of category plugin '" + cPlug.Name + "' is skipped.");
                }
                catch(Exception exc)
                {
                    _rs(((i * 100) / Extensil.CategoryHandlers.Count), "Failed to install category plugin '" + cPlug.Name + "'. " + exc.Message + ".");
                    result.Add(new Exception("Failed to install category plugin '" + cPlug.Name + "'. " + exc.Message + "."));
                }
            }
            i = 0;
            foreach(Plugin cPlug in Extensil.ProbeHandlers)
            {
                i++;
                try
                {
                    if(ConfigureProbeHandler(cPlug))
                        _rs(((i * 100) / Extensil.ProbeHandlers.Count), "Successfully installed handler plugin '" + cPlug.Name + "'.");
                    else
                        _rs(((i * 100) / Extensil.ProbeHandlers.Count), "Installation of handler plugin '" + cPlug.Name + "' is skipped.");
                }
                catch(Exception exc)
                {
                    _rs(((i * 100) / Extensil.ProbeHandlers.Count), "Failed to install handler plugin '" + cPlug.Name + "'. " + exc.Message + ".");
                    result.Add(new Exception("Failed to install handler plugin '" + cPlug.Name + "'. " + exc.Message + "."));
                }
            }
            return result;
        }
        bool ConfigureCategoryHandler(Plugin cPlug)
        {
            string path = cPlug.Path;
            string[] catClass = path.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            string hpath = PluginAssembly.GetPluginHandler(_pluginFolder + "\\" + catClass[1].Trim());
            if(String.IsNullOrEmpty(hpath))
                throw new Exception("Custom category " + catClass[1].Trim() + " could not be found.");

            try
            {
                if(!File.Exists(hpath))
                    throw new Exception("No assembly found at path " + hpath + ".");

                _rs(10, "Installing file " + hpath);
                Assembly asm = PluginAssembly.Load(hpath);
                if(asm == null)
                    throw new Exception(hpath + " not a valid assembly");
                Type type = asm.GetType(catClass[0].Trim());
                
                if(type == null)
                    throw new Exception("No class found with name '" + catClass[0].Trim() + "'.");

                if(!PluginAssembly.HasAncestorBaseType(type, typeof(BaseCategory)))
                    throw new Exception("Category class '" + catClass[0].Trim() + 
                        "' must be derived from type BaseCategory.");
            }
            catch(Exception exc)
            {
                throw new Exception("Invalid Category information. " + LinkedException(exc));
            }
            PluginHandler cat = new PluginHandler();
            cat.Name = cPlug.Name;
            cat.Path = catClass[0].Trim() + ", " + CategorySection.GetConfig().RootFolder + "/" + catClass[1].Trim();
            cat.Version = cPlug.Version;
            cat.Hidden = cPlug.Hidden;
            cat.AutoStart = cPlug.AutoStart;
            cat.PlugName = (new FileInfo(_pluginPath)).Name;
            cat.HotKey = cPlug.HotKey;
            foreach(AddElement element in cPlug.AppSettings)
                cat.CategorySettings.Add(element.Key, element.Value);
            PluginHandler checkCat = CategorySection.GetConfig().CategoryHandlers[cPlug.Name];
            bool deploy = false;
            if(checkCat == null)
            {
                using(ConfigModifier cm = new ConfigModifier())
                {
                    cm.AddCategoryHandler(cat);
                }
                deploy = true;
            }
            else
            {
                Version verExisting = new Version(checkCat.Version);
                Version verNew = new Version(cPlug.Version);
                if((verExisting.CompareTo(verNew) > 0 && AlertBox.ShowDialog("A higher version of category '" + checkCat.Name + "' - '" + checkCat.Version + "' is already installed. \r\n Do you really want to degrade to older version '" + cPlug.Version + "'?", "Dupicate category.", AlertBoxButtons.YesNo) == AlertResult.Yes)
                    ||
                    (verExisting.CompareTo(verNew) < 0 && AlertBox.ShowDialog("Category '" + checkCat.Name + "' - '" + checkCat.Version + "' is already installed. \r\n Do you want to upgrade this to higher version '" + cPlug.Version + "'?", "Dupicate category.", AlertBoxButtons.YesNo) == AlertResult.Yes)
                    ||
                    (verExisting.CompareTo(verNew) == 0 && AlertBox.ShowDialog("Category '" + checkCat.Name + "' with same version '" + checkCat.Version + "' is already installed. \r\n Do you want to repair the category?", "Dupicate category.", AlertBoxButtons.YesNo) == AlertResult.Yes))
                {
                    using(ConfigModifier cm = new ConfigModifier())
                    {
                        cm.EditCategoryHandler(cat.Name, cat);
                        deploy = true;
                    }
                }
            }
            if(deploy)
            {
                File.Copy(hpath,
                    Application.StartupPath + "/" + CategorySection.GetConfig().RootFolder + "/" + new FileInfo(hpath).Name, true);
                foreach(IncludeFile file in cPlug.Dependencies)
                {
                    File.Copy(_pluginFolder + "\\" + file.Path,
                    Application.StartupPath + "/" + CategorySection.GetConfig().RootFolder + "/" + file.Path, true);
                }
                return true;
            }
            else
                return false;
        }
        bool ConfigureProbeHandler(Plugin cPlug)
        {
            string path = cPlug.Path;
            string[] catClass = path.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            string hpath = PluginAssembly.GetPluginHandler(_pluginFolder + "\\" + catClass[1].Trim());
            if(String.IsNullOrEmpty(hpath))
                throw new Exception("Custom Handler " + catClass[1].Trim() + " could not be found.");

            try
            {
                Assembly asm = PluginAssembly.Load(hpath);
                Type type = asm.GetType(catClass[0].Trim());
                if(type == null)
                    throw new Exception("No class found with name '" + catClass[0].Trim() + "'.");
                if(!PluginAssembly.HasAncestorBaseType(type, typeof(BaseProbeHandler)))
                    throw new Exception("Handler class '" + catClass[0].Trim() +
                        "' must be derived from type BaseProbe.");
            }
            catch(Exception exc)
            {
                throw new Exception("Invalid Handler information. " + LinkedException(exc));
            }
            PluginHandler cat = new PluginHandler();
            cat.Name = cPlug.Name;
            cat.Path = catClass[0].Trim() + ", " + CategorySection.GetConfig().RootFolder + "/" + catClass[1].Trim();
            cat.Version = cPlug.Version;
            cat.Hidden = cPlug.Hidden;
            cat.AutoStart = cPlug.AutoStart;
            cat.PlugName = (new FileInfo(_pluginPath)).Name;
            cat.HotKey = cPlug.HotKey;
            foreach(AddElement element in cPlug.AppSettings)
                cat.CategorySettings.Add(element.Key, element.Value);
            PluginHandler checkCat = CategorySection.GetConfig().ProbeHandlers[cPlug.Name];
            bool deploy = false;
            if(checkCat == null)
            {
                using(ConfigModifier cm = new ConfigModifier())
                {
                    cm.AddProbeHandler(cat);
                }
                deploy = true;
            }
            else
            {
                Version verExisting = new Version(checkCat.Version);
                Version verNew = new Version(cPlug.Version);
                if((verExisting.CompareTo(verNew) > 0 && AlertBox.ShowDialog("A higher version of handler '" + checkCat.Name + "' - '" + checkCat.Version + "' is already installed. \r\n Do you really want to degrade to older version '" + cPlug.Version + "'?", "Dupicate handler.", AlertBoxButtons.YesNo) == AlertResult.Yes)
                    ||
                    (verExisting.CompareTo(verNew) < 0 && AlertBox.ShowDialog("Handler '" + checkCat.Name + "' - '" + checkCat.Version + "' is already installed. \r\n Do you want to upgrade this to higher version '" + cPlug.Version + "'?", "Dupicate handler.", AlertBoxButtons.YesNo) == AlertResult.Yes)
                    ||
                    (verExisting.CompareTo(verNew) == 0 && AlertBox.ShowDialog("Handler '" + checkCat.Name + "' with same version '" + checkCat.Version + "' is already installed. \r\n Do you want to repair the handler?", "Dupicate handler.", AlertBoxButtons.YesNo) == AlertResult.Yes))
                {
                    using(ConfigModifier cm = new ConfigModifier())
                    {
                        cm.EditProbeHandler(cat.Name, cat);
                        deploy = true;
                    }
                }
            }
            if(deploy)
            {
                File.Copy(hpath,
                    Application.StartupPath + "/" + CategorySection.GetConfig().RootFolder + "/" + new FileInfo(hpath).Name, true);
                foreach(IncludeFile file in cPlug.Dependencies)
                {
                    File.Copy(_pluginFolder + "\\" + file.Path,
                    Application.StartupPath + "/" + CategorySection.GetConfig().RootFolder + "/" + file.Path, true);
                }
                return true;
            }
            else
                return false;
        }
        void ExtractZipFile()
        {
            ZipFile zf = null;
            try
            {
                FileStream fs = File.OpenRead(_pluginPath);
                zf = new ZipFile(fs);
                foreach(ZipEntry zipEntry in zf)
                {
                    if(!zipEntry.IsFile)
                    {
                        continue;           
                    }
                    String entryFileName = zipEntry.Name;

                    byte[] buffer = new byte[4096];    
                    Stream zipStream = zf.GetInputStream(zipEntry);
                    String fullZipToPath = Path.Combine(_pluginFolder, entryFileName);
                    string directoryName = Path.GetDirectoryName(fullZipToPath);
                    if(directoryName.Length > 0)
                        Directory.CreateDirectory(directoryName);
                    using(FileStream streamWriter = File.Create(fullZipToPath))
                    {
                        StreamUtils.Copy(zipStream, streamWriter, buffer);
                    }
                }
            }
            finally
            {
                if(zf != null)
                {
                    zf.IsStreamOwner = true;
                    zf.Close();
                }
            }
        }
        string LinkedException(Exception exc)
        {
            if(exc == null)
                return "Null Exception";
            return exc.Message + (exc.InnerException == null ? "" : "\r\n\t" + LinkedException(exc.InnerException));
        }
    }
}
